
package com.scalar.chiptrack.proxy;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Calendar;
import java.util.Date;
import java.util.StringTokenizer;

/**
* SocketIoBase simply blocks on reading from one socket, then simply passed the
* data to the other socket.
*
*/

class SocketIoBase extends Thread {

    private InputStream Sinput = null;
    private OutputStream Soutput = null;
    private ProxySocket Parent = null;
    protected int iLogging = 0 ;
    private SocketIoBase Peer = null;
    private Socket s1 = null;
    private Socket s2 = null;
    private int iTerminate = 0 ;
    private int iDataCapture ;
    private Logger Log ;
    private String MyName, sSentinal ;
    private byte[] InBuffer1024 = new byte[1024] ;
    private byte[] OutBuffer = new byte[16384] ;
    private byte[] Sentinal = new byte[4] ;
    private FileOutputStream dcRw ;
    private PrintStream ps ;
    private int iHaveRead, iToWrite, iWritten ;
    private static long lLastIo = System.currentTimeMillis() ;
    private StringBuffer sbHex ;
    private StringBuffer sbVal ;
    private FileOutputStream dataOut = null;
    private static String fileName = null;

    public void SetPeer(SocketIoBase Peer) {
        this.Peer = Peer ;
    }

    public synchronized void CleanUp() throws IOException {
        iTerminate = 1 ;

        try {
            if (s1 != null) { s1.close(); }
            else { Sinput.close(); }

            if (s2 != null) { s2.close() ; }
            else { Soutput.close(); }

            Sinput.close() ;
            Soutput.close() ;
            if(iDataCapture > 0) {
                dcRw.flush() ;
                dcRw.close() ;
            }

            dataOut.close();
        }
        catch(IOException e) {
            if(iLogging > 0) {
                Log.It("Cleanup had problem " + e, iLogging) ;
            }
            throw e ;
        }
    }


    public void Setup (ProxySocket Parent, Socket sNew1, Socket sNew2,
                       String MyName, Logger Log, int iLogging,
                       int iDataCapture, FileOutputStream dcRw, String sSentinal) {
        this.Parent = Parent ;
        this.s1 = sNew1 ;
        this.s2 = sNew2 ;
        this.MyName = MyName ;
        this.Log = Log ;
        this.iLogging = iLogging ;
        this.iDataCapture = iDataCapture ;
        this.dcRw = dcRw ;
        //this.ps = new PrintStream(dcRw) ;
        this.sSentinal = sSentinal ;

        try {
            Sinput = new BufferedInputStream(s1.getInputStream()) ;
            if (s2 != null) {
                Soutput = new BufferedOutputStream(s2.getOutputStream()) ;
            }

            if (fileName == null) {
                fileName = MyName;
            }
            dataOut = new FileOutputStream (fileName + ".Out", true);
        }
        catch(IOException e) {}
        sbHex = new StringBuffer() ;
        sbVal = new StringBuffer() ;


    }

    public void run() {
        this.setName(MyName) ;

        Sentinal = sSentinal.getBytes() ;

        // read from Sinput

        if(iLogging > 0) {
            Log.It("Starting up", iLogging) ;
        }

        iHaveRead = -1 ;

        try {
            iHaveRead = Sinput.read(InBuffer1024, 0, 1024) ;
            if (Soutput == null) {
                Soutput =  new BufferedOutputStream(doSetup(InBuffer1024));
            }
        }
        catch(IOException e) {
            Log.It("Error on Input " + e, iLogging) ;
            iHaveRead = -1 ;
        }

        if(iLogging > 0) {
            Log.It("Read " + iHaveRead + " bytes", iLogging ) ;
        }

        while(iHaveRead != -1) {
            // call process line
            if(iDataCapture > 0) {
                try {
                    synchronized(Parent) {
                        DumpBuffer(iDataCapture) ;
                    }
                }
                catch(IOException e) {
                    Log.It("Problem writing data capture file" + e, iLogging) ;
                    iDataCapture = 0 ;
                }
            }

            iToWrite = ProcData(iHaveRead) ;

            // write to Soutput
            if(iLogging > 0) {
                Log.It("Writing " + iToWrite + " bytes", iLogging ) ;
            }

            try {
                Soutput.write(OutBuffer, 0, iToWrite) ;
                Soutput.flush() ;

                dataOut.write (OutBuffer, 0, iToWrite);
                dataOut.flush();
            }
            catch(IOException e) {
                Log.It("Error on Output " + e, iLogging) ;
                iHaveRead = -1 ;
                break ;
            }

            // be kind to everyone else
            yield() ;

            try {
                iHaveRead = Sinput.read(InBuffer1024, 0, 1024) ;
            }
            catch(IOException e) {
                Log.It("Error on Input " + e, iLogging) ;
                iHaveRead = -1 ;
                break ;
            }

            if(iLogging > 0) {
                Log.It("Read " + iHaveRead + " bytes", iLogging ) ;
            }
        }

        if(iLogging > 0) {
            Log.It("Lost the socket", iLogging ) ;
        }

        if(iTerminate == 0) {
            try {
                if(iLogging > 0) {
                    Log.It("CleanUp Peer", iLogging ) ;
                }

                Peer.CleanUp() ;
            }
            catch(IOException e) {
                Log.It("Problem with Cleanup Peer " + e, iLogging) ;
            }

            if(iLogging > 0) {
                Log.It("Telling Parent", iLogging ) ;
            }
            Parent.ConnectionLost() ;

            if(iLogging > 0) {
                Log.It("CleanUp me", iLogging ) ;
            }

            try {
                CleanUp() ;
            }
            catch(IOException e) {
                Log.It("Problem with Cleanup me " + e, iLogging) ;
            }
        }
        if(iLogging > 0) {
            Log.It("... and bye", iLogging ) ;
        }
    }


    // mod. the input Buffer, and write results to the output Buffer
    // return the length of the output Buffer

    private int ProcData(int iReadd) {

        if(iLogging > 0) {
            Log.It("In ProcData, data length " + iReadd, iLogging) ;
        }

        // simply copy input to output here
        System.arraycopy(InBuffer1024, 0, OutBuffer, 0, iReadd) ;

        return(iReadd) ;
    }

    void DumpBuffer(int iType) throws IOException {
        long lTmp ;
        int i = 0 ;
        int l = 0 ;
        int iIndex, iLoops, iLeftOver ;
        String sHexBuf = new String() ;
        String sAscBuf = new String() ;

        if(iType == 1) {
            dcRw.write(Sentinal, 0, 4) ;
            dcRw.write(InBuffer1024, 0, iHaveRead) ;
            dcRw.flush() ;
            return ;
        }

        lTmp = System.currentTimeMillis() - lLastIo ;

        Date today = new Date() ;
        Calendar cal = Calendar.getInstance();
        ps.println ("S " + sSentinal + " " + cal.get(Calendar.YEAR) + "/" + (cal.get(Calendar.MONTH) + 1) + "/" + cal.get(Calendar.DATE) + " " +
                    cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND) + " " + iHaveRead + "b " + lTmp + "mS") ;

        sbHex.setLength(0) ;
        sbVal.setLength(0) ;

        // create two string buffers containing printable representations of
        // the byte array. The spacer values are so the lineup of the
        // characters match when printed.

        sHexBuf = bytesToHex(InBuffer1024, iHaveRead, 1) ;
        sAscBuf = bytesToChar(InBuffer1024, iHaveRead, 2) ;

        // tut tut. I hardcoded values for speed from here onwards

        iLoops = iHaveRead / 16 ;
        iLeftOver = iHaveRead % 16 ;

        sbHex.append("X ") ;
        sbVal.append("C ") ;

        i = 0 ;
        l = 0 ;
        iIndex = 0 ;

        for(; i < iLoops ; i++, iIndex += 48) {
            sbHex.append(sHexBuf.substring(iIndex, iIndex + 48)) ;
            sbVal.append(sAscBuf.substring(iIndex, iIndex + 48)) ;

            l += 16 ;

            sbVal.append(" " + (l - 1)) ;
            ps.println(sbHex) ;
            ps.println(sbVal) ;
            ps.println("_") ;
            sbHex.setLength(0) ;

            sbVal.setLength(0) ;
            sbHex.append("X ") ;
            sbVal.append("C ") ;
        }

        if(iLeftOver > 0) {
            sbHex.append(sHexBuf.substring(iIndex, iIndex + (iLeftOver * 3))) ;
            sbVal.append(sAscBuf.substring(iIndex, iIndex + (iLeftOver * 3))) ;
            l += iLeftOver ;

            sbVal.append(" " + (l - 1)) ;
            ps.println(sbHex) ;
            ps.println(sbVal) ;
        }

        ps.println("# ======") ;
        ps.flush() ;
        lLastIo = System.currentTimeMillis() ;
    }

    public static final String bytesToHex (byte[] a, int iLength, int iSpacer) {
        StringBuffer s = new StringBuffer() ;

        for(int i = 0 ; i < iLength ; i++) {
            s.append(Character.forDigit((a[i] >> 4) & 0xf, 16)) ;
            s.append(Character.forDigit(a[i] & 0xf, 16)) ;

            if(iSpacer > 0) {
                if(iSpacer > 1) {
                    for(int j = 0 ; j < iSpacer ; j++) {
                        s.append(' ') ;
                    }
                }
                else {
                    s.append(' ') ;
                }
            }
        }
        return(s.toString()) ;
    }

    public static final String bytesToChar(byte[] a, int iLength, int iSpacer) {
        StringBuffer s = new StringBuffer() ;

        for(int i = 0 ; i < iLength ; i++) {
            if((char)a[i] < ' ' || (char)a[i] > 127) {
                s.append(".") ;
            }
            else {
                s.append((char)a[i]) ;
            }
            if(iSpacer > 0) {
                if(iSpacer > 1) {
                    for(int j = 0 ; j < iSpacer ; j++) {
                        s.append(' ') ;
                    }
                }
                else {
                    s.append(' ') ;
                }
            }
        }
        return(s.toString()) ;
    }

    public static final String byteToHex (int a) {
        StringBuffer s = new StringBuffer() ;

        s.append(Character.forDigit((a >> 4) & 0xf, 16)) ;
        s.append(Character.forDigit(a & 0xf, 16)) ;

        return(s.toString()) ;
    }


    private OutputStream doSetup(byte[] InBuffer1024) throws IOException {

        BufferedReader reader = new BufferedReader(new InputStreamReader (new ByteArrayInputStream (InBuffer1024)));
        String firstLine = reader.readLine();

        StringTokenizer st = new StringTokenizer (firstLine);
        st.nextToken();
        String nextToken = st.nextToken();
        if (nextToken.startsWith("http://")) {
            nextToken = nextToken.substring(6);
        }
        st = new StringTokenizer (nextToken, "/");

        String sDestIpAddress = st.nextToken();
        int iDestPortNumber = 80;

        int colonIndex = sDestIpAddress.indexOf(':');
        if (colonIndex != -1) {
            iDestPortNumber = Integer.parseInt(sDestIpAddress.substring (colonIndex+1));
            sDestIpAddress = sDestIpAddress.substring (0, colonIndex);
        }

        System.out.println ("-----> sDestIpAddress = " + sDestIpAddress + " iDestPortNumber = " + iDestPortNumber);
        Socket soOutbound = new Socket(InetAddress.getByName(sDestIpAddress), iDestPortNumber);

        SocketIoBase sInbound = new SocketIoBase() ;
        sInbound.Setup (this.Parent, soOutbound, this.s1, "SOCKET NeW" + System.currentTimeMillis(), Log, iLogging, iDataCapture, dcRw, "NeW>") ;

        // pass peer thread to each other
        this.SetPeer (sInbound);
        sInbound.SetPeer (this);

        // start them up and running
        if(iLogging > 0) {
            Log.It("Starting up socket handlers", iLogging) ;
        }
        sInbound.start();

        return soOutbound.getOutputStream();
    }
}